/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.core.base.dao;

import vip.dexian.common.query.Filter;
import vip.dexian.common.query.Listable;
import vip.dexian.common.query.Order;
import vip.dexian.common.query.Page;
import vip.dexian.common.query.Pageable;
import vip.dexian.core.base.bean.BaseEntity;
import vip.dexian.core.base.query.BaseQuery;
import jakarta.persistence.LockModeType;

import java.io.Serializable;
import java.util.List;

/**
 * 数据库操作基础Dao
 * JPA持久化实体状态分为：
 * 1.Transient(瞬时状态)：可以理解为新new的一个对象，没有被持久化，不处于entityManager中的对象
 * <p>
 * 2.Managed(托管状态)：处于entityManager的管理之中
 * <p>
 * 3.Persisted(持久化状态)：与entityManager有关系，已被持久化，调用persist方法以后，实体类就为托管状态
 * <p>
 * 4.Detached(游离状态)：对象和entityManager解除了关系
 * <p>
 * 5. Removed(删除状态)：调用entityManager.remove()，对象有关联的ID，并且在entityManager管理下
 * <p>
 * 为什么还是使用if is null 来判断是否为空，而不是使用Optional。 在使用jpa的查询、更新或者删除操作， if is null的判断相对简单。
 * <p>
 * 最重要的一点就是， 持久化上下文的关系
 *
 * @author 挺好的 2023年05月30日 15:42
 */
public interface BaseDao<T extends BaseEntity <ID>, ID extends Serializable> {

    /**
     * 根据ID查找实体类。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param id
     *         实体类ID
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    T find (ID id);


    /**
     * 根据ID查找实体类。当id为null或者实体类不存在，抛出异常
     *
     * @param id
     *         id
     * @param args
     *         国际化参数占位
     *
     * @return T
     *
     * @throws vip.dexian.core.exception.EntityNotFoundException
     *         id == null || 无法找到数据
     */
    T findByValidationIfNull (ID id, Object... args);

    /**
     * 根据ID查找实体类，并且使用悲观锁
     *
     * @param id
     *         实体类ID
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    T findByPessimisticWrite (ID id);

    /**
     * 根据ID查找实体类。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param id
     *         实体类ID
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     *         当该参数不传递的时候，同find(ID)方法一致
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    T find (ID id, LockModeType lockModeType);

    /**
     * 查找单个对象
     *
     * @param filters
     *         查找条件
     *
     * @return 1. filters is empty，返回null。2. 无法查找到数据，返回null。
     */
    T find (List <Filter> filters);

    /**
     * 查找单个对象
     *
     * @param filter
     *         查找条件
     *
     * @return @return 1. filter == null || filter is not valid，返回null。2. 无法查找到数据，返回null。
     */
    T find (Filter filter);

    /**
     * 只查找1条数据。注意该方法默认的排序（如果未指定排序条件）。 如果继承自{@link vip.dexian.core.base.bean.BaseEntity}则按ID降序，
     * 继承自{@link vip.dexian.core.base.bean.OrderEntity} || {@link vip.dexian.core.base.bean.UniqueOrderEntity}则按order升序
     *
     * @param orders
     *         排序条件。
     *
     * @return 如果存在则返回，不存在返回null
     */
    T get (List <Order> orders);

    /**
     * 查找列表
     *
     * @param listable
     *         列表参数
     *
     * @return 查找的实体类列表
     *
     * @see Listable
     */
    List <T> findList (Listable listable);

    /**
     * 根据ID查询列表，该方法会校验会将重复的ID去除，使用IN查询。
     *
     * @param ids
     *         id列表
     *
     * @return 对应的实体列表
     */
    List <T> findList (List <ID> ids);

    /**
     * 查找单个结果。注意该结果的排序方式， 如果继承自BaseEntity，默认按ID 降序，
     * <p>
     * 继承自{@link vip.dexian.core.base.bean.OrderEntity} || {@link vip.dexian.core.base.bean.UniqueOrderEntity}则按order升序
     *
     * @param listable
     *         搜索数据
     *
     * @return 查找的实体类
     */
    T find (Listable listable);

    /**
     * 分页查找数据。
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     *
     * @return 分页数据
     */
    Page <T> findPage (Pageable pageable);

    /**
     * 分页查找数据，当{@link BaseDao#findPage(vip.dexian.common.query.Pageable)}无法满足查询需求时，可以通过该方法扩展。
     * <p>
     * 默认情况下，当前方法同{@link BaseDao#findPage(vip.dexian.common.query.Pageable)}功能一致，需要扩展，请在对应的Dao中重写该方法
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     * @param query
     *         自定义查询条件
     * @param <Q>
     *         自定义查询条件
     *
     * @return 分页数据
     */
    <Q extends BaseQuery> Page <T> findPage (Pageable pageable, Q query);

    /**
     * 统计
     *
     * @param filters
     *         搜索条件
     *
     * @return select count(*)的数量
     */
    long count (Filter... filters);

    /**
     * 统计
     *
     * @param filters
     *         搜索条件
     *
     * @return select count(*)的数量
     */
    long count (List <Filter> filters);

    /**
     * 查询某条记录是否存在，此时使用LIMIT 1的写法，避免使用SELECT COUNT
     *
     * @param filters
     *         查询条件
     *
     * @return 指定条件存在，返回true
     */
    boolean exists (List <Filter> filters);

    /**
     * 获取实体类的ID
     *
     * @param entity
     *         实体类
     *
     * @return 返回实体类的ID
     */
    ID getIdentifier (T entity);

    /**
     * 实体类是否处于托管状态，如果处于托管状态，对于实体类的更新都会同步到数据库中
     *
     * @param entity
     *         实体类
     *
     * @return 是否是托管状态
     */
    boolean isManaged (T entity);

    /**
     * 实体类是否已加载
     *
     * @param entity
     *         实体类
     *
     * @return
     */
    boolean isLoaded (T entity);

    /**
     * 实体类的某个属性是否已加载
     *
     * @param entity
     *         实体类
     * @param attributeName
     *         属性名称
     *
     * @return 是否已加载
     */
    boolean isLoaded (T entity, String attributeName);

    /**
     * 保存实体类。持久化数据到数据库， 持久化完毕之后，实体类为托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param entity
     *         持久化的实体，不可为空
     */
    void persist (T entity);

    /**
     * 1. 该方法与persist方法不同，persist没有返回值，该实例会添加到上下文中并对该实例进行管理，在同一个事务中，之后对于实例的任何属性进行修改，会发生update语句。
     * 2. merge方法返回实例副本（创建实体的新实例），该副本在上下文中并且受管理，在同一个事务中，
     * 之后对于实例副本的任何属性进行修改，会发生update语句。原实例则不被管理（所以对于原实例属性的任何修改，都不会发出update语句）。
     * <p>
     * 合并实体类，非托管状态的entity调用该方法，会执行插入操作。托管状态的则会进行更新操作
     *
     * <p>
     * ================瞬时状态=====================
     * 保存瞬时状态数据（没有id, 并且数据库中没有对应的数据），此时将持久化这个对象，并且返回这个持久化对象的引用。
     * ================瞬时状态=====================
     * <p>
     * ================瞬时状态=====================
     * 保存瞬时状态数据（有id, 并且数据库中有对应的数据），此时将持久化这个对象，并且返回这个持久化对象的引用。
     * ================瞬时状态=====================
     *
     * <p>
     * ================游离状态=====================
     * 进行一次查询，如果对象存在，则更新该对象，如果不存在，则插入一条数据，并且返回这个持久化对象的引用
     * ================游离状态=====================
     *
     * <p>
     * ================托管状态=====================
     * 当实体类处于托管状态，调用该方法会执行更新操作
     * ================托管状态=====================
     *
     * @param entity
     *         实体类。
     *
     * @return 合并后的实例副本，该实例处于托管状态，在同一个事务中，任何对该实例副本进行的修改将会发出update语句
     */
    T merge (T entity);

    /**
     * 删除实体类。该方法会执行物理删除，一定要根据实际的业务逻辑来决定调用的是逻辑删除还是物流删除
     *
     * @param entity
     *         实体类
     */
    void remove (T entity);

    /**
     * 刷新实体类，使对象的状态和数据表中对应的记录保持一致。强制执行一次select。 只有在托管状态的实体类才可以执行refresh
     *
     * @param entity
     *         实体类. entity == null || entity.isNew() 不进行任何操作
     */
    void refresh (T entity);

    /**
     * 刷新实体类，使对象的状态和数据表中对应的记录保持一致。强制执行一次select。 只有在托管状态的实体类才可以执行refresh
     *
     * @param entity
     *         实体类。 entity == null || entity.isNew() 不进行任何操作
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     *         当该参数不传递的时候，同refresh(T)方法一致
     */
    void refresh (T entity, LockModeType lockModeType);

    /**
     * 从上下文中删除给定的实体（变为游离状态/脱管状态），在调用之前如果有对实体进行未刷新的更改（包括删除）将不会同步到数据库。之前引用游离实体的将继续引用。
     *
     * @param entity
     *         要变为游离状态的实体类
     */
    void detach (T entity);

    /**
     * 获取当前实体的锁类型。 要调用该方法必须满足以下条件：
     * <p>
     * 1. 必须要在事务中调用该方法
     * 2. 实体类不能为空并且要有id
     * 3. 实体类处于托管状态
     *
     * @param entity
     *         实体。
     *
     * @return 返回锁类型。
     *
     * @see org.hibernate.LockMode
     */
    LockModeType getLockMode (T entity);

    /**
     * 为指定实体加锁。调用该方法要满足以下条件：
     * <p>
     * 1、 实体类必须处于托管状态
     * 2、必须要在事务中调用该方法
     *
     * @param entity
     *         实体
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     */
    void lock (T entity, LockModeType lockModeType);

    /**
     * 清空所有上下文中关联的实体。
     */
    void clear ();

    /**
     * 刷新缓存。如果不调用flush方法， 什么时候执行插入或者更新无法确定。 当执行了flush以后， 会立即执行insert or update 语句。
     */
    void flush ();

}
